﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Compression;

namespace MTS
{
    class Region
    {
        private FileInfo filename;
        private bool[,] chunksAvailable;
        private Int32[,] offsets;
        private Int32[,] lengths;
        private Int32[,] lastModified;
        private RenderSettings settings;
        private long worldX, worldY;

        private static int SectorSize = 4096;
        private int chunkCount = 0;

        public int ChunkCount
        {
            get
            {
                return this.chunkCount;
            }
        }

        public long WorldX
        {
            get
            {
                return worldX;
            }
        }

        public long WorldY
        {
            get
            {
                return worldY;
            }
        }

        public Region(String filename, RenderSettings settings)
        {
            this.filename = new FileInfo(filename);
            this.chunksAvailable = new bool[32, 32];
            this.offsets = new Int32[32, 32];
            this.lengths = new Int32[32, 32];
            this.lastModified = new Int32[32, 32];
            this.settings = settings;

            String[] parts = this.filename.Name.Split(".".ToCharArray());
            if (parts.Length == 4)
            {
                // valid region
                worldX = int.Parse(parts[1].Trim());
                worldY = int.Parse(parts[2].Trim());
            }

            if (!this.filename.Exists)
            {
                throw new FileNotFoundException("Region {0} does not exist", this.filename.FullName);
            }
            else
            {
                loadHeaderInformation();
            }
        }

        private void loadHeaderInformation()
        {
            using (FileStream inFile = filename.OpenRead())
            {
                using (BinaryReader inReader = new BinaryReader(inFile))
                {
                    this.chunkCount = 0;

                    // read the offsets/lengths
                    for (int index = 0; index < SectorSize / 4; index++)
                    {
                        int currentX = (index % 32);
                        int currentY = (int)Math.Floor((double)index / 32) % 32;
                        byte[] offsetBytes = inReader.ReadBytes(3);
                        byte[] sectorBytes = inReader.ReadBytes(1);
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(offsetBytes);
                            Array.Reverse(sectorBytes);
                        }

                        int offset = offsetBytes[0] + (offsetBytes[1] << 8) + (offsetBytes[2] << 16);
                        int length = sectorBytes[0];
                        if (offset != 0)
                        {
                            this.chunksAvailable[currentX, currentY] = true;
                            this.offsets[currentX, currentY] = offset;
                            this.lengths[currentX, currentY] = length;
                            this.chunkCount++;
                        }
                        else
                        {
                            this.chunksAvailable[currentX, currentY] = false;
                        }
                    }
                    inReader.BaseStream.Position = 4096;
                    for (int index = 0; index < SectorSize / 4; index++)
                    {
                        // read the last modification date
                        int currentX = (index % 32);
                        int currentY = (int)Math.Floor((double)index / 32) % 32;
                        
                        byte[] modificationDate = inReader.ReadBytes(4);
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(modificationDate);
                        }

                        if (this.chunksAvailable[currentX, currentY] == true)
                        {
                            this.lastModified[currentX, currentY] = BitConverter.ToInt32(modificationDate, 0);
                        }
                    }
                }
            }
        }

        public Int32 lastReadTime(int localX, int localY)
        {
            return lastModified[localX, localY];
        }

        public bool ChunkExists(int localX, int localY)
        {
            return chunksAvailable[localX, localY];
        }

        public List<LevelChunk> getChunks()
        {
            List<LevelChunk> chunks = new List<LevelChunk>();
            for (int x = 0; x < chunksAvailable.GetLength(0); x++)
            {
                for (int y = 0; y < chunksAvailable.GetLength(1); y++ )
                {
                    if (chunksAvailable[x, y])
                    {
                        LevelChunk newChunk = new LevelChunk(this, settings, (this.worldY * 32 + y) * -1, this.worldX * 32 + x, x, y, lastModified[x, y]);
                        chunks.Add(newChunk);
                    }
                }
            }
            return chunks;
        }

        public byte[] ReadChunk(long localX, long localY)
        {
            byte[] output;
            using (FileStream inFile = filename.OpenRead())
            {
                using (BinaryReader inReader = new BinaryReader(inFile))
                {
                    Int32 offset = offsets[localX, localY];
                    inReader.BaseStream.Position = offset * SectorSize;
                    byte[] lengthBytes = inReader.ReadBytes(4);
                    byte[] compressionSchemeBytes = inReader.ReadBytes(1);
                    if (BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(lengthBytes);
                        Array.Reverse(compressionSchemeBytes);
                    }
                    int length = BitConverter.ToInt32(lengthBytes, 0);
                    inReader.BaseStream.Position += 2;
                    byte[] rawData = inReader.ReadBytes(length - 5);
                    using (MemoryStream rawStream = new MemoryStream(rawData))
                    {
                        using (MemoryStream bytesOut = new MemoryStream())
                        {
                            using (DeflateStream decompressor = new DeflateStream(rawStream, CompressionMode.Decompress))
                            {
                                try
                                {
                                    decompressor.CopyTo(bytesOut);
                                }
                                catch (System.IO.InvalidDataException GzipException)
                                {
#if DEBUG
                                    Console.Out.WriteLine("Chunk with bad header: " + filename.FullName + " offset " + offset * SectorSize + " length " + length + " compression type " + compressionSchemeBytes[0]);
#endif
                                }
                            }
                            output = bytesOut.ToArray();
                        }
                    }
                }
            }
            return output;
        }
    }
}
